/* 
 * File:   Transporte.cpp
 * Author: superoverpower
 * 
 * Created on 1 de Julho de 2011, 15:09
 */

#include "Transporte.h"
#include <vector>
#include <iostream>


using namespace std;
Transporte::Transporte() : Layer(0) {

        this->envioPendente = new vector<ProcessamentoDePacoteEnviado*>();
        this->recebimentoPendente = new vector<ProcessamentoDePacoteRecebido*>();
}

Transporte::Transporte(const Transporte& orig) : Layer(0) {
}

Transporte::~Transporte() {
}
int Transporte::getIP(Bits* data){
        return data->takeInfo(IP_OFFSET, IP_LENGTH);
}
int Transporte::getIDConexao(Bits* data){
      return data->takeInfo(ID_CON_OFFSET, ID_CON_LENGTH);

}
void Transporte::newMessageToSend(Bits* dadosEnviar){
        printf("Camada de Transporte - Mensagem que vai ser processada para enviar ");
        dadosEnviar->println();

        int ipDestino = getIP(dadosEnviar);
        printf("Camada de Transporte - IP de destino: %d\n", ipDestino);
        int idConexao = gerarIdConexao(ipDestino);
        printf("Camada de Transporte - ID de conexao gerada: %d\n", idConexao);

        //preparacao dos pacotes
        ProcessamentoDePacoteEnviado* proc = new ProcessamentoDePacoteEnviado(dadosEnviar, idConexao, this);

        //coloca na lista a espera do ack
        envioPendente->push_back(proc);

        int numPacotes = proc->getNumeroDePacotes();
        printf("Camada de Transporte - Numero de pacotes: %d\n", numPacotes);

        //cria a mensagem de solicitacao de conexao
        Bits* mensagemDeConexao = criarMensagemDeConexao(ipDestino, idConexao, numPacotes);
        printf("Camada de Transporte - Mensagem de conexao: ");
        mensagemDeConexao->println();

        //envia a mensagem de solicitacao de conexao
        getDownLayer()->send(mensagemDeConexao);

}



void Transporte::newReceivedMessage(Bits *dadosReceber){
    printf("Camada de Transporte - Dados recebidos da camada de baixo: ");
    dadosReceber->println();
    //se a mensagem recebida e de solicitacao de conexao
    if (eMensagemDeConexao(dadosReceber)) {
            printf("Camada de Transporte - E' mensagem de conexao\n");
            int ipOrigem = getIP(dadosReceber);
            printf("Camada de Transporte - IP de origem: %d\n", ipOrigem);
            int idConexao = getIDConexao(dadosReceber);
            printf("Camada de Transporte - ID da conexao: %d\n", idConexao);
            int numPacotes = getNumPacotes(dadosReceber);
            printf("Camada de Transporte - Numero de pacotes: %d\n", numPacotes);

            //se prepara para receber os pacotes
            ProcessamentoDePacoteRecebido *proc = new ProcessamentoDePacoteRecebido(ipOrigem, idConexao, numPacotes, this);

            //coloca proc na lista de espera por pacotes
            recebimentoPendente->push_back(proc);

            //manda a mensagem de ack para quem solicitou a conexao
            Bits* mensagemDeAck = criarMensagemDeAck(ipOrigem, idConexao);
            printf("Camada de Transporte - Mensagem de ACK: ");
            mensagemDeAck->println();
            printf("Camada de Transporte - Enviando para camada de baixo\n");

            getDownLayer()->send(mensagemDeAck);

        } //se a mensagem recebida e de ack
    else if (eMensagemDeAck(dadosReceber)) {
            printf("Camada de Transporte - E' mensagem de ACK\n");
            int ipOrigem = getIP(dadosReceber);
            printf("Camada de Transporte - IP de origem: %d\n", ipOrigem);
            int idConexao = getIDConexao(dadosReceber);
            printf("Camada de Transporte - ID da conexao: %d\n", idConexao);

            //procura na lista de espera por acks uma conexao com esse ip e esse id
            vector<ProcessamentoDePacoteEnviado*>::iterator it;
            for (it=envioPendente->begin(); it != envioPendente->end(); it++)
                if ((*it)->getIpDestino() == ipOrigem && (*it)->getIdConexao() == idConexao) {
                    (*it)->enviaPacotes();
                    envioPendente->erase(it);
                    break;
                }

        } //se for a transferencia de um pacote de uma conexao ja estabelecida
        else {
            printf("Camada de Transporte - Transferencia de pacote para conexao ja estabelecida\n");
            int ipOrigem = getIP(dadosReceber);
            printf("Camada de Transporte - IP de origem: %d\n", ipOrigem);
            int idConexao = getIDConexao(dadosReceber);
            printf("Camada de Transporte - ID da conexao: %d\n", idConexao);

            //procura na lista de espera por pacotes uma conexao com esse ip e esse id
            vector<ProcessamentoDePacoteRecebido*>::iterator it;
            for (it=recebimentoPendente->begin(); it != recebimentoPendente->end(); it++) {

                if ((*it)->getIpOrigem() == ipOrigem && (*it)->getIdConexao() == idConexao) //trata o pacote recebido e se todos os pacotes da mensagem ja chegaram,
                //manda a mensagem para a camada de aplicacao e remove essa conexao da
                //lista de espera por pacotes
                {
                    if ((*it)->pacoteRecebido(dadosReceber)) {
                        recebimentoPendente->erase(it);
                    }
                }

                break;

            }

        }


}

bool Transporte::eMensagemDeAck(Bits* data) {
    return data->get(E_ACK_OFFSET);
}

int Transporte::getNumPacotes(Bits* data) {
        return data->takeInfo(NUM_PACOTES_OFFSET, NUM_PACOTES_LENGTH);
}

bool Transporte::eMensagemDeConexao(Bits* data) {
        return data->get(E_CON_OFFSET);
}

 //quadro retornado: 0 1 ip_destino id_conexao
Bits* Transporte::criarMensagemDeAck(int ipDestino, int idConexao) {

    int complementoDePacote = 2;

    Bits* data =
            new Bits(E_CON_LENGTH + E_ACK_LENGTH + IP_LENGTH + ID_CON_LENGTH + complementoDePacote);

    data->reset(E_CON_OFFSET);
    data->set(E_ACK_OFFSET);

    data->putInfo(IP_OFFSET, IP_LENGTH, ipDestino);
    data->putInfo(ID_CON_OFFSET, ID_CON_LENGTH, idConexao);

    return data;

}

int Transporte::gerarIdConexao(int ipDestino) {

        int id = 0;
        vector<ProcessamentoDePacoteEnviado*>::iterator it;
        for (  it= envioPendente->begin() ; it != envioPendente->end(); it++ ) {
            if ((*it)->getIpDestino() == ipDestino) {
                if ((*it)->getIdConexao() == id) {
                    id++;
                    continue;
                }
            }
        }

        return id;

    }

Bits* Transporte::criarMensagemDeConexao(int ipDestino, int idConexao, int numPacotes) {

        int complementoDePacote = 8;

        Bits *data = new Bits(E_CON_LENGTH + E_ACK_LENGTH + IP_LENGTH + ID_CON_LENGTH + NUM_PACOTES_LENGTH + complementoDePacote);

        data->set(E_CON_OFFSET);
        data->reset(E_ACK_OFFSET);

        data->putInfo(IP_OFFSET, IP_LENGTH, ipDestino);
        data->putInfo(ID_CON_OFFSET, ID_CON_LENGTH, idConexao);
        data->putInfo(NUM_PACOTES_OFFSET, NUM_PACOTES_LENGTH, numPacotes);

        return data;

    }

Transporte::ProcessamentoDePacoteEnviado::ProcessamentoDePacoteEnviado(Bits* mensagemCompleta, int idConexao, Transporte *transp){
            this->transporte = transp;
            this->mensagemCompleta = mensagemCompleta;

            this->ipDestino = mensagemCompleta->takeInfo(0, IP_LENGTH);
            this->idConexao = idConexao;

            numPacotes = ((mensagemCompleta->length() - IP_LENGTH) % PAYLOAD_LENGTH == 0)
                    ? (mensagemCompleta->length() - IP_LENGTH) / PAYLOAD_LENGTH
                    : (mensagemCompleta->length() - IP_LENGTH) / PAYLOAD_LENGTH + 1;

            pacotes = new std::vector<Bits*>(numPacotes);

            for (int i = 0; i < pacotes->size(); i++)
                (*pacotes)[ i ] = new Bits( PACOTE_LENGHT );

            preenchePacotes();

}

int Transporte::ProcessamentoDePacoteEnviado::getIdConexao(){
       return idConexao;

}

int Transporte::ProcessamentoDePacoteEnviado::getIpDestino(){
    return ipDestino;
}

int Transporte::ProcessamentoDePacoteEnviado::getNumeroDePacotes(){
    return numPacotes;
}

//preenchimento dos pacotes a partir da mensagem completa
void Transporte::ProcessamentoDePacoteEnviado::preenchePacotes() {

            int numeroDeOrdem = 0;
            vector<Bits*>::iterator it;
           for ( it= pacotes->begin() ; it != pacotes->end(); it++ ) {

                //ip de destino
                (*it)->putInfo(IP_OFFSET, IP_LENGTH, ipDestino);

                //o pacote nao e de solicitacao nem de aceitacao de conexao
                (*it)->reset(E_CON_OFFSET);
                (*it)->reset(E_ACK_OFFSET);

                //id da conexao
                (*it)->putInfo(ID_CON_OFFSET, ID_CON_LENGTH, idConexao);

                //numero de ordem de um *it
                (*it)->putInfo(NUM_ORDEM_OFFSET, NUM_ORDEM_LENGTH, numeroDeOrdem);

                int offset = numeroDeOrdem * PAYLOAD_LENGTH + IP_LENGTH;
                int length = (mensagemCompleta->length() - offset >= PAYLOAD_LENGTH) ? PAYLOAD_LENGTH : mensagemCompleta->length() - offset;
                int start = PAYLOAD_OFFSET;

                //preenchimento do payload do pacote
                (*it)->copyBits(
                    mensagemCompleta,
                    offset,
                    length,
                    start
                );

                numeroDeOrdem++;

            }

        }

void Transporte::ProcessamentoDePacoteEnviado::enviaPacotes(){
    printf("Camada de Transporte - Enviando pacotes\n");
    vector<Bits*>::iterator it;
    int i = 0;
    for ( it= pacotes->begin() ; it != pacotes->end(); it++,i++ ) {
//        printf("oi ");
//        (*it)->println();
        transporte->getDownLayer()->send(*it);
    }

}

//classe interna que trata um pacote recebido de uma conexao:
//organiza os pacotes e manda a mensagem completa para a camada de aplicacao
Transporte::ProcessamentoDePacoteRecebido::ProcessamentoDePacoteRecebido(int ipOrigem, int idConexao, int numeroDePacotes, Transporte *transp){
    this->transporte = transp;
    this->ipOrigem = ipOrigem;
    this->idConexao = idConexao;

    this->numeroDePacotes = numeroDePacotes;

    this->pacotesRecebidos = (bool*) calloc(numeroDePacotes, sizeof(bool));

    this->mensagemCompleta = new Bits(IP_LENGTH + numeroDePacotes * PAYLOAD_LENGTH);
    this->mensagemCompleta->putInfo(IP_OFFSET, IP_LENGTH, ipOrigem);

}

int Transporte::ProcessamentoDePacoteRecebido::getIdConexao(){
    return idConexao;
}

int Transporte::ProcessamentoDePacoteRecebido::getIpOrigem(){
    return ipOrigem;
}

//forca o envio, mesmo se nem todos os pacotes estiverem chegado
void Transporte::ProcessamentoDePacoteRecebido::enviaMensagemCompletaParaAplicacao(){
    printf("Camada de Transporte - Enviando para camada de aplicacao\n");
    transporte->getUpLayer()->receive(mensagemCompleta);
}

//estrutura do quadro enviado para a aplicacao ipOrigem mensagemCompleta
//caso retorne true, esse ProcessamendoDePacoteRecebido e removido da lista
bool Transporte::ProcessamentoDePacoteRecebido::pacoteRecebido(Bits* data){
    printf("Camada de Transporte - Pacote recebido: ");
    data->println();
    int numOrdemPacote = getNumOrdem(data);
    printf("Camada de Transporte - Num Ordem: %d\n", numOrdemPacote);
    //copia o payload desse pacote recebido para a posicao correta na
    //mensagem recebida
    mensagemCompleta->copyBits(data, PAYLOAD_OFFSET, PAYLOAD_LENGTH, (numOrdemPacote * PAYLOAD_LENGTH + IP_LENGTH));

    pacotesRecebidos[numOrdemPacote] = true;

    //verifica se todos os pacotes dessa mensagem ja chegaram
    //se sim, manda para a aplicacao
    if (todosOsPacotesRecebidos()) {
        printf("Camada de Transporte - Mensagem completa remontada: ");
        mensagemCompleta->println();
        enviaMensagemCompletaParaAplicacao();
        return true;
    }

    return false;

}

int Transporte::ProcessamentoDePacoteRecebido::getNumOrdem(Bits* data){
    return data->takeInfo(NUM_ORDEM_OFFSET, NUM_ORDEM_LENGTH);
}

bool Transporte::ProcessamentoDePacoteRecebido::todosOsPacotesRecebidos(){
        for(int i = 0; i < numeroDePacotes;i++) {
        if (!pacotesRecebidos[i]) {
            return false;
        }
    }
    return true;
}

